/*
 * Copyright (c) 2009 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */
#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include "confdata.h"
#include "util.h"

run2_selfoptions_t *
run2_selfoptions_create (void)
{
  run2_selfoptions_t * v = (run2_selfoptions_t *)
    run2_malloc (sizeof (run2_selfoptions_t));
  v->args = NULL;
  return v;
}

void
run2_selfoptions_delete (run2_selfoptions_t *arg)
{
  if (arg)
    {
      if (arg->args)
        {
          cdsl_dlist_destroy_list (arg->args, &free);
          arg->args = NULL;
        }
      free (arg);
    }
}

#define INDENT 2
static char *
indent_spacing (int level)
{
  int i = 0;
  char * s = (char *) run2_malloc (level * INDENT + 1);
  memset ((void *)s, (int)' ', level * INDENT);
  s[level * INDENT] = '\0';
  return s;
}

static void
run2_args_print (FILE *f, int level, cdsl_dlist_t *list)
{
  if (list && cdsl_dlist_size (list) > 0)
    {
      int i = 0;
      char * s = indent_spacing (level);
      cdsl_dlist_node_t *p;
      for (p = cdsl_dlist_begin (list);
           p != cdsl_dlist_end (list);
           p = cdsl_dlist_next (p))
        {
          fprintf (f, "%sargv[%d]: '%s'\n", s, i++, (char*)cdsl_dlist_value (p));
        }
      free (s);
    }
}

void
run2_selfoptions_print (FILE * f, int level, const char *name,
                        const run2_selfoptions_t *elem)
{
  char * s = indent_spacing (level);
  int i = 1;
  fprintf (f, "%s%s:\n", s, name);
  free (s);
  run2_args_print(f, level + 1, elem->args);
}

run2_env_spec_t *
run2_env_spec_create (void)
{
  run2_env_spec_t * v = (run2_env_spec_t *)
    run2_malloc (sizeof (run2_env_spec_t));
  v->set = NULL;
  v->prepend = NULL;
  v->append = NULL;
  return v;
}

run2_env_spec_t *
run2_env_spec_copy (run2_env_spec_t * arg)
{
  run2_env_spec_t * v = run2_env_spec_create ();
  v->set = NULL;
  v->prepend = NULL;
  v->append = NULL;
  if (arg)
  {
    if (arg->set)
      v->set = run2_strdup (arg->set);
    if (arg->prepend && cdsl_dlist_size (arg->prepend) > 0)
      {
        int i = 0;
        cdsl_dlist_node_t *p;
        for (p = cdsl_dlist_begin (arg->prepend);
             p != cdsl_dlist_end (arg->prepend);
             p = cdsl_dlist_next (p))
          {
            run2_env_spec_prepend (v, (char*)cdsl_dlist_value (p));
          }
      }
    if (arg->append && cdsl_dlist_size (arg->append) > 0)
      {
        int i = 0;
        cdsl_dlist_node_t *p;
        for (p = cdsl_dlist_begin (arg->append);
             p != cdsl_dlist_end (arg->append);
             p = cdsl_dlist_next (p))
          {
            run2_env_spec_append (v, (char*)cdsl_dlist_value (p));
          }
      }
  }
  return v;
}

static void *
run2_env_spec_copy_wrap (void *arg)
{
   return (run2_env_spec_t *) run2_env_spec_copy ((run2_env_spec_t *)arg);
}

void
run2_env_spec_delete (run2_env_spec_t *arg)
{
  if (arg)
    {
      if (arg->set)
        {
          free (arg->set);
          arg->set= NULL;
        }
      if (arg->prepend)
        {
          cdsl_dlist_destroy_list (arg->prepend, &free);
          arg->prepend= NULL;
        }
      if (arg->append)
        {
          cdsl_dlist_destroy_list (arg->append, &free);
          arg->append= NULL;
        }
      free (arg);
    }
}

static void
run2_env_spec_delete_wrap (void *arg)
{
  run2_env_spec_delete ((run2_env_spec_t *)arg);
}

void
run2_env_spec_print  (FILE * f, int level, const char *name,
                      const run2_env_spec_t *elem)
{
  char * s = indent_spacing (level);
  if (elem->set)
    fprintf (f, "%s%s[set]: '%s'\n", s, name, elem->set);
  if (elem->prepend && cdsl_dlist_size (elem->prepend) > 0)
    {
      int i = 0;
      cdsl_dlist_node_t *p;
      for (p = cdsl_dlist_begin (elem->prepend);
           p != cdsl_dlist_end (elem->prepend);
           p = cdsl_dlist_next (p))
        {
          fprintf (f, "%s%s[pre]: '%s'\n", s, name,
                   (char*)cdsl_dlist_value (p));
        }
    }
  if (elem->append && cdsl_dlist_size (elem->append) > 0)
    {
      int i = 0;
      cdsl_dlist_node_t *p;
      for (p = cdsl_dlist_begin (elem->append);
           p != cdsl_dlist_end (elem->append);
           p = cdsl_dlist_next (p))
        {
          fprintf (f, "%s%s[app]: '%s'\n", s, name,
                   (char*)cdsl_dlist_value (p));
        }
    }
}


run2_env_t *
run2_env_create (void)
{
  run2_env_t * v = (run2_env_t *)
    run2_malloc (sizeof (run2_env_t));
  v->map = NULL;
  return v;
}

void
run2_env_delete (run2_env_t *arg)
{
  if (arg)
    {
      if (arg->map)
	{
	  cdsl_map_delete (arg->map);
	  arg->map = NULL;
	}
      free (arg);
    }
}

void
run2_env_print  (FILE * f, int level, const char *name,
                 const run2_env_t *elem)
{
  char * s = indent_spacing (level);
  fprintf (f, "%s%s:\n", s, name);
  free (s);
  if (elem->map && !cdsl_map_empty (elem->map))
    {
      cdsl_map_iter_t * iter = cdsl_map_iter_new (elem->map);
      if (!iter)
        {
          errorMsg ("(%s[%d]): %s", __func__, __LINE__, strerror(errno));
          exit (1);
        }
      cdsl_map_first (elem->map, iter);
      while (!cdsl_map_iter_end (iter))
        {
          const char *var = (const char *)cdsl_map_key (iter);
          const run2_env_spec_t *spec = (const run2_env_spec_t *)
            cdsl_map_value (iter);
          run2_env_spec_print (f, level+1, var, spec);
          cdsl_map_next (iter);
        }
      cdsl_map_iter_delete (iter);
    }
}

run2_global_t *
run2_global_create (void)
{
  run2_global_t * v = (run2_global_t *)
    run2_malloc (sizeof (run2_global_t));
  v->env = NULL;
  v->tgt = NULL;
  return v;
}

void
run2_global_delete (run2_global_t *arg)
{
  if (arg)
    {
      if (arg->env)
	{
          run2_env_delete (arg->env);
	  arg->env = NULL;
	}
      free (arg);
    }
}

void run2_global_print (FILE * f, int level, const char *name,
                          const run2_global_t *elem)
{
  char * s = indent_spacing (level);
  fprintf (f, "%s%s:\n", s, name);
  free (s);
  if (elem->env)
    {
      run2_env_print (f, level+1, "Environment", elem->env);
    }
  if (elem->tgt)
    {
      run2_tgt_spec_print (f, level+1, "Target", elem->tgt);
    }
}

run2_tgt_spec_t *
run2_tgt_spec_create (void)
{
  run2_tgt_spec_t * v = (run2_tgt_spec_t *)
    run2_malloc (sizeof (run2_tgt_spec_t));
  v->filename = NULL;
  v->startin  = NULL;
  v->args     = NULL;
  return v;
}

void
run2_tgt_spec_delete (run2_tgt_spec_t *arg)
{
  if (arg)
    {
      if (arg->filename)
	{
          free (arg->filename);
	  arg->filename = NULL;
	}
      if (arg->startin)
	{
          free (arg->startin);
	  arg->startin = NULL;
	}
      if (arg->args)
	{
          cdsl_dlist_destroy_list (arg->args, &free);
	  arg->args = NULL;
	}
      free (arg);
    }
}

void
run2_tgt_spec_print (FILE * f, int level, const char *name,
                     const run2_tgt_spec_t *elem)
{
  char * s = indent_spacing (level);
  fprintf (f, "%s%s:\n", s, name);
  free (s);
  s = indent_spacing (level + 1);
  if (elem->filename && *elem->filename)
    {
      fprintf (f, "%sfilename: '%s'\n", s, elem->filename);
    }
  if (elem->startin) /* empty is ok */
    {
      fprintf (f, "%sstartin: '%s'\n", s, elem->startin);
    }
  free (s);
  run2_args_print(f, level + 1, elem->args);
}

run2_tgtopts_t *
run2_tgtopts_create (void)
{
  run2_tgtopts_t * v = (run2_tgtopts_t *)
    run2_malloc (sizeof (run2_tgtopts_t));
  v->env = NULL;
  v->tgt = NULL;
  return v;
}

void
run2_tgtopts_delete (run2_tgtopts_t *arg)
{
  if (arg)
    {
      if (arg->env)
	{
          run2_env_delete (arg->env);
	  arg->env = NULL;
	}
      if (arg->tgt)
	{
          run2_tgt_spec_delete (arg->tgt);
	  arg->tgt = NULL;
	}
      free (arg);
    }
}

void run2_tgtopts_print (FILE * f, int level, const char *name,
                         const run2_tgtopts_t *elem)
{
  char * s = indent_spacing (level);
  fprintf (f, "%s%s:\n", s, name);
  free (s);
  if (elem->env)
    {
      run2_env_print (f, level+1, "Environment", elem->env);
    }
  if (elem->tgt)
    {
      run2_tgt_spec_print (f, level+1, "Target", elem->tgt);
    }
}

run2_confdata_t *
run2_confdata_create (void)
{
  run2_confdata_t * v = (run2_confdata_t *)
    run2_malloc (sizeof (run2_confdata_t));
  v->selfopt = NULL;
  v->global  = NULL;
  v->gdi     = NULL;
  v->x11     = NULL;
  return v;
}

void
run2_confdata_delete (run2_confdata_t *arg)
{
  if (arg)
    {
      if (arg->selfopt)
	{
          run2_selfoptions_delete (arg->selfopt);
	  arg->selfopt = NULL;
	}
      if (arg->global)
	{
          run2_global_delete (arg->global);
	  arg->global= NULL;
	}
      if (arg->gdi)
	{
          run2_tgtopts_delete (arg->gdi);
	  arg->gdi= NULL;
	}
      if (arg->x11)
	{
          run2_tgtopts_delete (arg->x11);
	  arg->x11= NULL;
	}
      free (arg);
    }
}

void
run2_confdata_print (FILE * f, int level, const char *name,
                     const run2_confdata_t *elem)
{
  char * s = indent_spacing (level);
  fprintf (f, "%s%s:\n", s, name);
  free (s);
  if (elem->selfopt)
    {
      run2_selfoptions_print (f, level+1, "SelfOptions", elem->selfopt);
    }
  if (elem->global)
    {
      run2_global_print (f, level+1, "Global", elem->global);
    }
  if (elem->gdi)
    {
      run2_tgtopts_print (f, level+1, "GDI", elem->gdi);
    }
  if (elem->x11)
    {
      run2_tgtopts_print (f, level+1, "X11", elem->x11);
    }
}

static int
hasNonWS (const char *s)
{
  size_t len = strlen (s);
  int rv = 0;
  int i;
  const char *p = s;
  for (i = 0; i < len && rv == 0; i++)
    if (!isspace (*p++))
      rv = 1;
  return rv;
}

static int
run2_add_arg (const char    *caller,
              cdsl_dlist_t **list,
              const char    *entry)
{
  if (!*list)
    {
      *list = cdsl_dlist_new_list ();
      if (!*list)
        {
          errorMsg ("(%s[%d] [%s]): %s", __func__, __LINE__,
                    caller, strerror(errno));
          exit (1);
        }
    }
  char *data = run2_strdup (entry);
  cdsl_dlist_node_t *n = cdsl_dlist_insert_after (
    *list, cdsl_dlist_end (*list), (void *)data);
  if (!n)
    {
      errorMsg ("(%s[%d] [%s]): unable to add %s to args",
                __func__, __LINE__, caller, entry);
      exit (1);
    }
  return 0;
}

int
run2_selfoptions_add_arg (run2_selfoptions_t *elem,
                          const char           *entry)
{
  int rv = 0;
  if (entry && *entry && hasNonWS (entry))
  {
    rv = run2_add_arg ("run2_selfoptions_add_arg", &(elem->args), entry);
  }
  return rv;
}

int run2_env_spec_set     (run2_env_spec_t *elem,
                           const char        *set)
{
  if (set) /* empty string is ok; means "unset" */
    {
      if (elem->set)
        {
          free (elem->set);
          elem->set = NULL;
        }
      elem->set = run2_strdup (set);
    }
  return 0;
}

int
run2_env_spec_prepend (run2_env_spec_t *elem,
                       const char        *prepend)
{
  int rv = 0;
  if (prepend && *prepend && hasNonWS (prepend))
  {
    /* STUB: tokenize */
    rv = run2_add_arg ("run2_env_spec_prepend", &(elem->prepend), prepend);
  }
  return rv;
}

int
run2_env_spec_append  (run2_env_spec_t *elem,
                       const char        *append)
{
  int rv = 0;
  if (append && *append && hasNonWS (append))
  {
    /* STUB: tokenize */
    rv = run2_add_arg ("run2_env_spec_append", &(elem->append), append);
  }
  return rv;
}

static int
stricmp_wrap(void *a, void *b)
{
  return stricmp ((const char *)a, (const char *)b);
}
static void *
run2_strdup_wrap(void *a)
{
  return (void *) run2_strdup ((const char *)a);
}

static int
validVarName(const char *s)
{
  size_t len = strlen (s);
  int rv = 1;
  int i;
  const char *p = s;
  for (i = 0; i < len && rv == 1; i++)
    {
      if (!isalnum(*p) && *p != '_')
        rv = 0;
    }
  return rv;
}

run2_env_spec_t *
run2_env_get_var (run2_env_t *elem,
                  const char   *varname)
{
  run2_env_spec_t *envspec;
  cdsl_map_iter_t   *iter;

  if (!varname || !*varname || !validVarName(varname))
    {
      errorMsg ("(%s[%d]): bad variable name `%s'",
                __func__, __LINE__, varname);
      exit (1);
    }
  if (!elem->map)
    {
      elem->map = cdsl_map_new (&stricmp_wrap,
                                &run2_strdup_wrap,
                                &free,
                                &run2_env_spec_copy_wrap,
                                &run2_env_spec_delete_wrap);
      if (!elem->map)
        {
          errorMsg ("(%s[%d]): %s", __func__,
                    __LINE__, strerror(errno));
          exit (1);
        }
    }

  /* first see if it is already present */
  iter = cdsl_map_iter_new (elem->map);
  if (!iter)
    {
      errorMsg ("(%s[%d]): %s", __func__, __LINE__, strerror(errno));
      exit (1);
    }
  if (cdsl_map_find (elem->map, (void *)varname, iter))
    {
      /* it is already present; return it */
      envspec = cdsl_map_value (iter);
    }
  else
    {
      envspec = run2_env_spec_create ();
      if (!cdsl_map_insert (elem->map, (void *)run2_strdup(varname), (void *)envspec))
        {
          errorMsg ("(%s[%d]): unable to add entry for variable `%s'",
                    __func__, __LINE__, varname);
          exit (1);
        }
      /* insert makes a copy, so clean up original and get pointer to copy */
      run2_env_spec_delete (envspec);
      if (!cdsl_map_find (elem->map, (void *)varname, iter))
      {
         errorMsg ("(%s[%d]): unable to retrieve just-added entry for variable `%s'",
                    __func__, __LINE__, varname);
         exit (1);
      }
      envspec = cdsl_map_value (iter);
    }
  cdsl_map_iter_delete (iter);
  return envspec;
}

run2_env_t *
run2_global_get_env (run2_global_t *elem)
{
  if (!elem->env)
    {
      elem->env = run2_env_create ();
      if (!elem->env)
        {
          errorMsg ("(%s[%d]): %s", __func__, __LINE__, strerror(errno));
          exit (1);
        }
    }
  return elem->env;
}

run2_tgt_spec_t *
run2_global_get_tgt (run2_global_t *elem)
{
  if (!elem->tgt)
    {
      elem->tgt = run2_tgt_spec_create ();
      if (!elem->tgt)
        {
          errorMsg ("(%s[%d]): %s", __func__, __LINE__, strerror(errno));
          exit (1);
        }
    }
  return elem->tgt;
}

int
run2_tgt_spec_add_arg (run2_tgt_spec_t *elem,
                       const char        *entry)
{
  int rv = 0;
  if (entry && *entry && hasNonWS (entry))
  {
    /* STUB: tokenize */
    rv = run2_add_arg ("run2_tgt_spec_add_arg", &(elem->args), entry);
  }
  return rv;
}

int
run2_tgt_spec_set_filename (run2_tgt_spec_t *elem,
                            const char        *filename)
{
  if (filename && *filename)
    {
      if (elem->filename)
        {
          free (elem->filename);
          elem->filename = NULL;
        }
      elem->filename = run2_strdup (filename);
    }
  return 0;
}

int
run2_tgt_spec_set_startin (run2_tgt_spec_t *elem,
                           const char        *startin)
{
  if (startin) /* empty string is ok; means "unset" */
    {
      if (elem->startin)
        {
          free (elem->startin);
          elem->startin = NULL;
        }
      elem->startin = run2_strdup (startin);
    }
  return 0;
}

run2_env_t *
run2_tgtopts_get_env (run2_tgtopts_t *elem)
{
  if (!elem->env)
    {
      elem->env = run2_env_create ();
      if (!elem->env)
        {
          errorMsg ("(%s[%d]): %s", __func__, __LINE__, strerror(errno));
          exit (1);
        }
    }
  return elem->env;
}

run2_tgt_spec_t *
run2_tgtopts_get_tgt (run2_tgtopts_t *elem)
{
  if (!elem->tgt)
    {
      elem->tgt = run2_tgt_spec_create ();
      if (!elem->tgt)
        {
          errorMsg ("(%s[%d): %s", __func__, __LINE__, strerror(errno));
          exit (1);
        }
    }
  return elem->tgt;
}

run2_selfoptions_t *
run2_confdata_get_selfoptions (run2_confdata_t *elem)
{
  if (!elem->selfopt)
    {
      elem->selfopt = run2_selfoptions_create ();
      if (!elem->selfopt)
        {
          errorMsg ("(%s[%d]): %s", __func__, __LINE__,
                    strerror(errno));
          exit (1);
        }
    }
  return elem->selfopt;
}

run2_global_t *
run2_confdata_get_global (run2_confdata_t *elem)
{
  if (!elem->global)
    {
      elem->global = run2_global_create ();
      if (!elem->global)
        {
          errorMsg ("(%s[%d]): %s", __func__, __LINE__,
                    strerror(errno));
          exit (1);
        }
    }
  return elem->global;
}

run2_tgtopts_t *
run2_confdata_get_gdi (run2_confdata_t *elem)
{
  if (!elem->gdi)
    {
      elem->gdi = run2_tgtopts_create ();
      if (!elem->gdi)
        {
          errorMsg ("(%s[%d]): %s", __func__, __LINE__,
                    strerror(errno));
          exit (1);
        }
    }
  return elem->gdi;
}

run2_tgtopts_t *
run2_confdata_get_x11 (run2_confdata_t *elem)
{
  if (!elem->x11)
    {
      elem->x11 = run2_tgtopts_create ();
      if (!elem->x11)
        {
          errorMsg ("(%s[%d]): %s", __func__,  __LINE__,
                    strerror(errno));
          exit (1);
        }
    }
  return elem->x11;
}
